1 module hip.assets.tilemap; 2 public import hip.api.data.tilemap; 3 public import hip.api.data.asset; 4 import hip.assets.texture; 5 import hip.config.opts; 6 import hip.assets.image; 7 import hip.data.json; 8 9 class HipTileset : HipAsset, IHipTileset 10 { 11 uint _columns; uint columns() const =>_columns; 12 13 ///Means where the tileset id starts 14 uint _firstGid; uint firstGid() const => _firstGid; 15 16 17 ///"image" in tiled 18 19 string _texturePath; string texturePath() const => _texturePath; 20 ///"imageheight" in tiled 21 uint _textureHeight; uint textureHeight() const => _textureHeight; 22 ///"imagewidth" in tiled 23 uint _textureWidth; uint textureWidth() const => _textureWidth; 24 IHipTexture _texture; IHipTexture texture() => _texture; 25 int _margin; int margin() const => _margin; 26 27 override string name() const => super.name; 28 29 ///Only available when loaded via .tsx 30 string _path; string path() const => _path; 31 int _spacing; int spacing() const => _spacing; 32 uint _tileHeight; uint tileHeight() const => _tileHeight; 33 uint _tileWidth; uint tileWidth() const => _tileWidth; 34 Tile[] _tiles; Tile[] tiles() => _tiles; 35 36 void setTexture(IHipTexture texture) 37 { 38 this._texture = texture; 39 this._textureWidth = texture.getWidth; 40 this._textureHeight = texture.getHeight; 41 } 42 43 // static if(hasTSXSupport) 44 // { 45 // import arsd.dom; 46 47 // static Tileset fromTSX(ubyte[] tsxData, string tsxPath, bool autoLoadTexture = true) 48 // { 49 // string xmlFile = cast(string)tsxData; 50 // auto document = new XmlDocument(xmlFile); 51 // auto tileset = document.querySelector("tileset"); 52 // return Tileset.fromXMLElement(tileset, tsxPath, autoLoadTexture); 53 // } 54 55 56 // static Tileset fromXMLElement(Element tileset, string tsxPath="", bool autoLoadTexture=true) 57 // { 58 // auto image = tileset.querySelector("image"); 59 60 // const uint tileCount = to!uint(tileset.getAttribute("tilecount")); 61 // Tileset ret = new Tileset(tileCount); 62 // ret.path = tsxPath; 63 64 // //Tileset 65 // ret.name = tileset.getAttribute("name"); 66 // ret.tileWidth = to!uint(tileset.getAttribute("tilewidth")); 67 // ret.tileHeight = to!uint(tileset.getAttribute("tileheight")); 68 // ret.columns = to!uint(tileset.getAttribute("columns")); 69 70 // //Image 71 // ret.texturePath = image.getAttribute("source"); 72 // ret.textureWidth = to!uint(image.getAttribute("width")); 73 // ret.textureHeight = to!uint(image.getAttribute("height")); 74 75 // if(autoLoadTexture) 76 // ret.loadTexture(); 77 78 // Element[] tiles = tileset.querySelectorAll("tile"); 79 80 // foreach(t; tiles) 81 // { 82 // Tile tile; 83 // tile.id = to!ushort(t.getAttribute("id")); 84 // Element anim = t.querySelector("animation"); 85 // if(anim !is null) 86 // { 87 // Element[] frames = anim.querySelectorAll("frame"); 88 // tile.animation = new TileAnimationFrame[frames.length]; 89 90 // foreach(f; frames) 91 // { 92 // TileAnimationFrame tFrame; 93 // tFrame.id = to!ushort(f.getAttribute("tileid")); 94 // tFrame.duration = to!int(f.getAttribute("duration")); 95 // } 96 // } 97 // } 98 99 // return ret; 100 // } 101 102 103 // static Tileset fromTSX(string tsxPath, bool autoLoadTexture = true) 104 // { 105 // void[] tsxData; 106 // if(!HipFS.read(tsxPath, tsxData)) 107 // { 108 // import hip.error.handler; 109 // ErrorHandler.showWarningMessage("Could not load TSX ", tsxPath); 110 // return null; 111 // } 112 // return fromTSX(cast(ubyte[])tsxData, tsxPath, autoLoadTexture); 113 // } 114 115 // protected static TileLayer tileLayerFromElement(Element l) 116 // { 117 // import hip.util.string:split; 118 // import hip.util.file:stripLineBreaks; 119 // TileLayer layer = new TileLayer(); 120 // layer.type = TileLayerType.TILE_LAYER; 121 // layer.id = to!ushort(l.getAttribute("id")); 122 // layer.name = l.getAttribute("name"); 123 // layer.width = to!uint(l.getAttribute("width")); 124 // layer.height = to!uint(l.getAttribute("height")); 125 // string[] data = l.querySelector("data").innerText.stripLineBreaks.split(","); 126 // layer.tiles.reserve(data.length); 127 // for(int i = 0; i < data.length;i++) 128 // layer.tiles~=to!ushort(data[i]); 129 130 // return layer; 131 // } 132 133 // protected static TileLayer objectLayerFromElement(Element objgroup) 134 // { 135 // TileLayer layer = new TileLayer(); 136 // layer.type = TileLayerType.OBJECT_LAYER; 137 // layer.id = toDefault!(ushort)(objgroup.getAttribute("id")); 138 // layer.name = objgroup.getAttribute("name"); 139 // Element[] objs = objgroup.querySelectorAll("object"); 140 // foreach(o; objs) 141 // { 142 // TileLayerObject obj; 143 // obj.gid = toDefault!(ushort)(o.getAttribute("gid")); 144 // obj.height = toDefault!(uint)(o.getAttribute("height")); 145 // obj.id = toDefault!(ushort)(o.getAttribute("id")); 146 // obj.name = (o.getAttribute("name")); 147 // obj.rotation= toDefault!(int)(o.getAttribute("rotation")); 148 // obj.type = (o.getAttribute("type")); 149 // obj.visible = toDefault!(bool)(o.getAttribute("visible")); 150 // obj.width = toDefault!(uint)(o.getAttribute("width")); 151 // obj.x = toDefault!(int)(o.getAttribute("x")); 152 // obj.y = toDefault!(int)(o.getAttribute("y")); 153 // Element[] props = o.querySelectorAll("properties"); 154 // foreach(p; props) 155 // { 156 // TileProperty tp; 157 // tp.name = p.getAttribute("name"); 158 // tp.type = p.getAttribute("type"); 159 // tp.value = p.getAttribute("value"); 160 // obj.properties[tp.name] = tp; 161 // } 162 // } 163 // return layer; 164 // } 165 // static Tilemap readTiledTMX(string tiledPath) 166 // { 167 // void[] tmxData; 168 // if(!HipFS.read(tiledPath, tmxData)) 169 // { 170 // import hip.error.handler; 171 // ErrorHandler.showWarningMessage("Could not read Tiled TMX from path ", tiledPath); 172 // return null; 173 // } 174 // return readTiledTMX(cast(ubyte[])tmxData, tiledPath); 175 // } 176 177 // static Tilemap readTiledTMX(ubyte[] tiledData, string tiledPath, bool autoLoadTexture = true) 178 // { 179 // Tilemap ret = new Tilemap(); 180 // string xmlFile = cast(string)tiledData; 181 // auto document = new XmlDocument(xmlFile); 182 // auto map = document.querySelector("map"); 183 // ret.path = tiledPath; 184 185 // ret.tiled_version = map.getAttribute("tiledVersion"); 186 // ret.orientation = map.getAttribute("orientation"); 187 // ret.width = to!uint(map.getAttribute("width")); 188 // ret.height = to!uint(map.getAttribute("height")); 189 // ret.tileWidth = to!uint(map.getAttribute("tilewidth")); 190 // ret.tileHeight = to!uint(map.getAttribute("tileheight")); 191 // ret.isInfinite = (to!uint(map.getAttribute("infinite")) == 1); 192 // ret.renderorder = map.getAttribute("renderorder"); 193 194 // auto tileset = document.querySelectorAll("tileset"); 195 196 // foreach(t; tileset) 197 // { 198 // string tsxPath = t.getAttribute("source"); 199 // Tileset set; 200 // if(tsxPath != null) 201 // set = Tileset.fromTSX(ret.getTSXPath(tsxPath), autoLoadTexture); 202 // else 203 // { 204 // set = Tileset.fromXMLElement(t, ret.getTSXPath("null")); 205 // //Using getTSXPath with any string, as it will be replaced later 206 // //For the texture path 207 // } 208 // set.firstGid = to!uint(t.getAttribute("firstgid")); 209 // ret.tilesets~=set; 210 // } 211 212 // auto layers = document.querySelectorAll("map > layer"); 213 // foreach(l; layers) 214 // { 215 // TileLayer layer = Tilemap.tileLayerFromElement(l); 216 // ret.layersArray~= layer; 217 // ret.layers[layer.name] = layer; 218 // } 219 220 // Element[] objGroups = document.querySelectorAll("map > objectgroup"); 221 // foreach(objGroup; objGroups) 222 // { 223 // TileLayer layer = Tilemap.objectLayerFromElement(objGroup); 224 // ret.layers[layer.name] = layer; 225 // } 226 227 // return ret; 228 // } 229 230 231 // } 232 // else static if(Version.HipTSX) 233 // { 234 // static assert(false, `Please call dub add arsd-official:dom for using TSX parser`); 235 // } 236 237 static HipTileset read (string path, void delegate(HipTileset self) onSuccess, void delegate() onError, uint firstGid = 1) 238 { 239 import hip.util.path; 240 switch(path.extension) 241 { 242 case "xml": 243 case "tmx": 244 throw new Exception("TMX/TSX parser was removed for making the engine smaller."); 245 case "tsj": 246 case "json": 247 return HipTileset.readJSON(path, firstGid, onSuccess, onError); 248 default: 249 assert(false, "Unrecognized extension for file "~path); 250 } 251 } 252 253 static HipTileset readFromMemory (string path, string data, void delegate(HipTileset) onSuccess, void delegate() onError, uint firstGid = 1) 254 { 255 import hip.util.path; 256 switch(path.extension) 257 { 258 case "xml": 259 case "tmx": 260 throw new Exception("TMX/TSX parser was removed for making the engine smaller."); 261 case "tsj": 262 case "json": 263 HipTileset ret = new HipTileset(0); 264 ret._path = path; 265 ret._firstGid = firstGid; 266 ret.loadJSON(parseJSON(data), onSuccess, onError); 267 return ret; 268 default: 269 assert(false, "Unrecognized extension for file "~path); 270 } 271 } 272 273 static HipTileset readJSON (string path, uint firstGid, void delegate(HipTileset self) onSuccess, void delegate() onError) 274 { 275 import hip.api.filesystem.hipfs; 276 import hip.console.log; 277 HipTileset tileset = new HipTileset(0); 278 tileset._path = path; 279 tileset._firstGid = firstGid; 280 281 HipFS.readText(path).addOnSuccess((in void[] data) 282 { 283 tileset.loadJSON(parseJSON(cast(string)data), onSuccess, onError); 284 return FileReadResult.free; 285 }).addOnError((err) 286 { 287 loglnWarn("Could not read file at path ", path," ", err); 288 }); 289 return tileset; 290 } 291 292 public static HipTileset readJSON (string path, uint firstGid, const JSONValue t, void delegate(HipTileset self) onSuccess, void delegate() onError) 293 { 294 HipTileset ret = new HipTileset(0); 295 ret._path = path; 296 ret._firstGid = firstGid; 297 ret.loadJSON(t, onSuccess, onError); 298 return ret; 299 } 300 301 private void loadJSON (const JSONValue t, void delegate(HipTileset self) onSuccess, void delegate() onError) 302 { 303 import hip.util.path; 304 if(t.hasErrorOccurred) 305 { 306 import hip.error.handler; 307 ErrorHandler.showErrorMessage("JSON Parsing Error on Tilemap", t.toString); 308 return onError(); 309 } 310 311 _tiles = new Tile[cast(uint)t["tilecount"].integer]; 312 _texturePath = t["image"].str; 313 if(!isAbsolutePath(_texturePath) && _path.length) 314 _texturePath = joinPath(dirName(_path), _texturePath).normalizePath; 315 _textureHeight = cast(uint)t["imageheight"].integer; 316 _textureWidth = cast(uint)t["imagewidth"].integer; 317 _columns = cast(ushort)t["columns"].integer; 318 _margin = cast(int)t["margin"].integer; 319 _name = t["name"].str; 320 _spacing = cast(int)t["spacing"].integer; 321 _tileHeight = cast(uint)t["tileheight"].integer; 322 _tileWidth = cast(uint)t["tilewidth"].integer; 323 324 if("tiles" in t) 325 { 326 foreach (currentTile; t["tiles"].array) 327 { 328 Tile tile; 329 tile.id = cast(ushort)currentTile["id"].integer; 330 331 foreach(prop; currentTile["properties"].array) 332 { 333 tile.properties[prop["name"].str] = propFromJSON(prop); 334 } 335 tiles[tile.id] = tile; 336 } 337 } 338 onSuccess(this); 339 } 340 341 342 import hip.util.data_structures; 343 static IHipTileset fromSpritesheet(Array2D_GC!IHipTextureRegion regions) 344 { 345 import hip.error.handler; 346 import hip.assets.texture; 347 ErrorHandler.assertExit(regions.getWidth > 0 && regions.getHeight > 0, "Invalid spritesheet"); 348 HipTileset t = new HipTileset(regions.getWidth * regions.getHeight); 349 t._name = "Created from Spritesheet"; 350 t._firstGid = 1; 351 t.setTexture(regions[0,0].getTexture); 352 t._tileWidth = regions[0,0].getWidth(); 353 t._tileHeight = regions[0,0].getHeight(); 354 int i = 0; 355 for(int y = 0; y < regions.getHeight; y++) 356 for(int x = 0; x < regions.getWidth; x++) 357 { 358 Tile* tile = &t.tiles[i++]; 359 tile.id = cast(ushort)i; 360 tile.region = regions[x, y]; //TODO: May use clone one day if direct assign doesn't fit 361 // t.region = (cast(HipTextureRegion)regions[x, y]).clone; 362 } 363 364 return t; 365 } 366 import hip.assets.textureatlas; 367 /** 368 * Untested. D's Associative Arrays aren't deterministic, this is subject to bug. 369 */ 370 static IHipTileset fromAtlas(HipTextureAtlas atlas) 371 { 372 HipTileset t = new HipTileset(cast(uint)atlas.frames.length); 373 t._firstGid = 1; 374 t._name = "Tileset from Atlas: "~atlas.name; 375 t.setTexture(atlas.texture); 376 int i = 0; 377 foreach(atlasFrame; atlas) 378 { 379 Tile* tile = &t.tiles[i++]; 380 if(!t.tileWidth) 381 { 382 t._tileWidth = atlasFrame.region.getWidth; 383 t._tileHeight = atlasFrame.region.getHeight; 384 } 385 //TODO: May use clone one day if direct assign doesn't fit. 386 tile.region = atlasFrame.region; 387 tile.id = cast(ushort)i; 388 } 389 return t; 390 } 391 392 this(uint tileCount) 393 { 394 super("HipTileset"); 395 _tiles = new Tile[tileCount]; 396 _typeID = assetTypeID!HipTileset; 397 } 398 IImage textureImage; 399 400 IImage loadImage(void delegate(IImage self) onSuccess, void delegate() onFailure) 401 { 402 import hip.error.handler; 403 import hip.api.filesystem.hipfs; 404 import hip.util.path; 405 if(textureImage is null) 406 { 407 ErrorHandler.assertExit(texturePath != "", "No texture path for loading tilemap texture"); 408 HipFS.read(texturePath) 409 .addOnError((string err) 410 { 411 ErrorHandler.showErrorMessage("Error loading image required by Tileset: "~texturePath, err); 412 onFailure(); 413 }) 414 .addOnSuccess((in ubyte[] imgData) 415 { 416 textureImage = new Image(texturePath, cast(ubyte[])imgData, onSuccess, onFailure); 417 return FileReadResult.free; 418 }); 419 } 420 return textureImage; 421 } 422 423 bool loadTexture() 424 { 425 import hip.error.handler; 426 import hip.assets.texture; 427 if(textureImage is null) 428 { 429 loadImage((_){loadTexture();}, (){}); 430 return false; 431 } 432 _texture = new HipTexture(textureImage); 433 int i = 0; 434 for(int y = margin; y < textureHeight; y+= (tileHeight+spacing)) 435 for(int x = margin, currCol = 0 ; currCol < columns; currCol++, x+= (tileWidth+spacing)) 436 { 437 Tile* t = &tiles[i]; 438 t.region = new HipTextureRegion(texture, x, y, x+tileWidth, y+tileHeight); 439 i++; 440 } 441 442 return texture !is null && texture.hasSuccessfullyLoaded(); 443 } 444 445 override void onFinishLoading(){} 446 override void onDispose(){} 447 override bool isReady() const {return _texture !is null;} 448 } 449 450 451 class HipTilemap : HipAsset, IHipTilemap 452 { 453 454 int _x, _y; 455 HipColor _color = HipColor.white; 456 float _scaleX = 1.0, _scaleY = 1.0; 457 float _rotation = 0; 458 459 string _path; 460 uint _width, _height; 461 bool _isInfinite; 462 HipTileLayer[string] _layers; 463 string _orientation; 464 string _renderOrder; 465 string _tiledVersion; 466 uint _tileWidth, _tileHeight; 467 468 this(uint width = 0, uint height = 0, uint tileWidth = 0, uint tileHeight = 0) 469 { 470 super("HipTilemap"); 471 _typeID = assetTypeID!HipTilemap; 472 this._width = width; 473 this._height = height; 474 this._tileWidth = tileWidth; 475 this._tileHeight = tileHeight; 476 } 477 478 ref int x() => _x; 479 ref int y() => _y; 480 ref HipColor color() => _color; 481 ref float scaleX() => _scaleX; 482 ref float scaleY() => _scaleY; 483 float scale() => _scaleX; 484 float scale(float sc) => _scaleX = _scaleY = sc; 485 ref float rotation() => _rotation; 486 487 ///Used for rendering order 488 string path() const => _path; 489 uint width() const => _width; 490 uint height() const => _height; 491 bool isInfinite() const => _isInfinite; 492 ref HipTileLayer[string] layers() => _layers; 493 string orientation() const => _orientation; 494 string renderorder() const => _renderOrder; 495 string tiled_version() const => _tiledVersion; 496 uint tileHeight() const => _tileHeight; 497 uint tileWidth() const => _tileWidth; 498 499 void setTileSize(uint tileWidth, uint tileHeight) 500 { 501 _tileWidth = tileWidth; 502 _tileHeight = tileHeight; 503 } 504 505 void addTileset(IHipTileset tileset){tilesets~= cast(HipTileset)tileset;} 506 507 protected HipTileLayer[] layersArray; 508 HipTileset[] tilesets; 509 this() 510 { 511 super("HipTilemap"); 512 _typeID = assetTypeID!HipTilemap; 513 } 514 515 IHipTileset getTilesetForID(ushort id) 516 { 517 if(tilesets.length == 0) 518 return null; 519 for(int i = 0; i < cast(int)tilesets.length-1; i++) 520 { 521 if(id >= tilesets[i].firstGid && id < tilesets[i+1].firstGid) 522 return tilesets[i]; 523 } 524 return tilesets[$-1]; 525 } 526 527 528 string getTSXPath(string tsxName) 529 { 530 import hip.util.path : replaceFileName; 531 return replaceFileName(path, tsxName); 532 } 533 534 private static TiledObjectTypes typeInObject(JSONValue o) 535 { 536 with ( TiledObjectTypes ) 537 { 538 if("ellipse" in o) return ellipse; 539 if("gid" in o) return tile; 540 if("point" in o) return point; 541 if("text" in o) return text; 542 if("polyline" in o) return line; 543 if("polygon" in o) return polygon; 544 return rect; 545 } 546 } 547 548 private static void parseObjectLayer(ref HipTileLayer layer, JSONValue[] objects) 549 { 550 import hip.util.array:uninitializedArray; 551 int objIndex = 0; 552 layer.objects = uninitializedArray!(TiledObject[])(objects.length); 553 foreach(JSONValue o; objects) 554 { 555 TiledObject obj; 556 557 obj.id = cast(ushort)o["id"].integer; 558 obj.name = o["name"].str; 559 obj.type = o["type"].str; 560 obj.visible = o["visible"].boolean; 561 562 563 obj.data.rect.x = cast(int) o["x"].floating; 564 obj.data.rect.rotation= cast(ushort)(o["rotation"].integer % 360); 565 obj.data.rect.y = cast(int) o["y"].floating; 566 obj.data.rect.height = cast(uint) o["height"].floating; 567 obj.data.rect.width = cast(uint) o["width"].floating; 568 obj.dataType = typeInObject(o); 569 570 switch(obj.dataType) with(TiledObjectTypes) 571 { 572 case text: 573 { 574 JSONValue txtObj = o["text"]; 575 obj.properties["__text"] = TileProperty(null, Variant.make(tryGetValue!string(txtObj, "text"))); 576 obj.properties["__fontfamily"] = TileProperty(null, Variant.make(tryGetValue!string(txtObj, "fontfamily"))); 577 obj.properties["__wrap"] = TileProperty(null, Variant.make(tryGetValue!bool(txtObj, "wrap"))); 578 } 579 break; 580 case line: 581 { 582 JSONValue[] line = o["polyline"].array; 583 int x = obj.data.rect.x; 584 int y = obj.data.rect.y; 585 foreach(i; 0..4) 586 obj.data.rect.getLine[i] = tryGetValue(line[i/2], i % 2 == 0 ? "x" : "y", 0) + (i % 2 == 0 ? x : y); 587 break; 588 } 589 case polygon: 590 { 591 JSONValue[] poly = o["polygon"].array; 592 obj.dataType = poly.length == 3 ? TiledObjectTypes.triangle : TiledObjectTypes.polygon; 593 int[2][] targetPoly = obj.data.triangle; 594 if(poly.length > 3) 595 targetPoly = new int[2][poly.length]; 596 foreach(i, v; poly) 597 { 598 targetPoly[i] = [ 599 tryGetValue(poly[i], "x", 0), 600 tryGetValue(poly[i], "y", 0) 601 ]; 602 } 603 if(poly.length > 3) 604 obj.data.polygon = targetPoly; 605 break; 606 } 607 case tile: 608 obj.tile.gid = cast(ushort)o["gid"].integer; 609 break; 610 default:break; 611 } 612 const(JSONValue)* v = ("properties" in o); 613 if(v != null) 614 { 615 foreach(p; v.array) //Properties 616 obj.properties[p["name"].str] = propFromJSON(p); 617 } 618 layer.objects[objIndex++] = obj; 619 } 620 } 621 622 static HipTilemap readTiledJSON (string mapPath, const ubyte[] tiledData, void delegate(HipTilemap) onSuccess, void delegate() onError) 623 { 624 import hip.data.json; 625 HipTilemap ret = new HipTilemap(); 626 ret._path = mapPath; 627 JSONValue json = parseJSON(cast(string)(tiledData)); 628 ret._height = cast(uint)json["height"].integer; 629 ret._isInfinite = json["infinite"].boolean; 630 ret._width = cast(uint)json["width"].integer; 631 ret._orientation= json["orientation"].str; 632 ret._renderOrder= json["renderorder"].str; 633 ret._tileHeight = cast(uint)json["tileheight"].integer; 634 ret._tileWidth = cast(uint)json["tilewidth"].integer; 635 636 foreach(l; json["layers"].array) 637 { 638 HipTileLayer layer = new HipTileLayer(ret); 639 640 //Check first the layer type. 641 layer.name = l["name"].str; 642 layer.type = l["type"].str; 643 layer.id = cast(ushort)l["id"].integer; 644 layer.opacity = l["opacity"].integer; 645 layer.visible = l["visible"].boolean; 646 layer.x = cast(int) l["x"].integer; 647 layer.y = cast(int) l["y"].integer; 648 if(layer.type == TileLayerType.OBJECT_LAYER) 649 { 650 parseObjectLayer(layer, l["objects"].array); 651 } 652 else if(layer.type == TileLayerType.TILE_LAYER) 653 { 654 auto layerData = l["data"].array; 655 layer.height = cast(uint) l["height"].integer; 656 layer.width = cast(uint) l["width"].integer; 657 layer.tiles.reserve(layerData.length); 658 foreach(d; layerData) 659 layer.tiles~= cast(ushort)d.integer; 660 } 661 662 const(JSONValue)* layerProp = ("properties" in l); 663 if(layerProp != null) 664 { 665 foreach(p; layerProp.array) 666 layer.properties[p["name"].str] = propFromJSON(p); 667 } 668 ret.layersArray~=layer; 669 ret._layers[layer.name] = layer; 670 } 671 672 size_t maxTilesets = json["tilesets"].array.length; 673 auto onTilesetLoad = delegate(HipTileset tileset) 674 { 675 ret.tilesets~= tileset; 676 if(ret.tilesets.length == maxTilesets) 677 onSuccess(ret); 678 }; 679 foreach(t; json["tilesets"].array) 680 { 681 import hip.util.path; 682 const(JSONValue)* source = ("source" in t); 683 uint firstGid = cast(ushort)t["firstgid"].integer; 684 685 ///Returns are being ignored since it is being handled on the onTilesetLoad 686 if(source !is null) 687 { 688 import hip.console.log; 689 loglnWarn("Reading from source: ", joinPath(dirName(mapPath), source.str).normalizePath); 690 HipTileset.read(joinPath(dirName(mapPath), source.str).normalizePath, onTilesetLoad, onError, firstGid); 691 } 692 else 693 HipTileset.readJSON(dirName(mapPath), firstGid, t, onTilesetLoad, onError); 694 } 695 696 697 698 return ret; 699 } 700 static void readTiledJSON (string tiledPath, void delegate(HipTilemap) onSuccess, void delegate() onError) 701 { 702 import hip.api.filesystem.hipfs; 703 HipFS.read(tiledPath).addOnSuccess((in ubyte[] data) 704 { 705 HipTilemap.readTiledJSON(tiledPath, cast(ubyte[])data, onSuccess, onError); 706 return FileReadResult.free; 707 }).addOnError((err) 708 { 709 import hip.error.handler; 710 ErrorHandler.showWarningMessage("Could not read Tiled TMX from path ", tiledPath); 711 onError(); 712 }); 713 } 714 715 716 ///Those arguments are actually synchronous on all platforms. This is to simulate JS API. 717 void loadImages(void delegate() onSuccess, void delegate() onFailure) 718 { 719 int counter = 0; 720 auto onSuccessInternal = delegate(IImage _) 721 { 722 if(++counter == tilesets.length) 723 onSuccess(); 724 }; 725 foreach(HipTileset tileset; tilesets) 726 tileset.loadImage(onSuccessInternal, onFailure); 727 } 728 729 bool loadTextures() 730 { 731 foreach(HipTileset tileset; tilesets) 732 if(!tileset.loadTexture()) 733 return false; 734 return true; 735 } 736 737 override void onFinishLoading(){} 738 override void onDispose(){} 739 override bool isReady() const {return true;} 740 741 742 } 743 744 private TileProperty propFromJSON(JSONValue v) 745 { 746 import hip.util.exception; 747 JSONValue* t = "type" in v; 748 TileProperty ret = void; 749 enforce(t !is null, "propFromJSON must have a 'type'"); 750 ret.type = t.str; 751 switch(ret.type) 752 { 753 case "object", "int": 754 ret.val = Variant.make(v["value"].integer); 755 break; 756 case "bool": 757 ret.val = Variant.make(v["value"].boolean); 758 break; 759 case "float": 760 ret.val = Variant.make(v["value"].floating); 761 break; 762 case "color", "string", "file": 763 ret.val = Variant.make(v["value"].str); 764 break; 765 default: 766 throw new Exception("Unknown property for TiledProperty of type "~ret.type); 767 } 768 return ret; 769 }